Release 10.1A: OpenEdge Development:
Progress 4GL Handbook


Generating a procedure listing file

To verify how Progress is scoping the record buffers, you can generate a listing file that contains various information about how Progress processes the procedure when you compile or run it.

To do this, use the LISTING option on the COMPILE statement:

  1. Save the procedure so that it has a name you can reference: testscope.p.
  2. Open another procedure window and enter this statement:
  3. COMPILE testscope.p LISTING testscope.lis. 
    

  4. Press F2 to run the COMPILE statement.
  5. Select File Open to open testscope.lis. Here is the code you should see:
  6. {} Line Blk 
    -- ---- --- 
      1   1  FOR EACH Customer BY creditLimit DESCENDING: 
      2   1     DISPLAY "Highest:" CustNum NAME CreditLimit  
      3   1         WITH 1 DOWN. 
      4   1     LEAVE. 
      5      END. 
      6      
      7      FOR EACH Customer WHERE state = "NH" BY CreditLimit DESCENDING: 
      8   1     DISPLAY CustNum NAME CreditLimit. 
      9      END. 
      10      
         File Name          Line     Blk. Type      Tran          Blk. Label 
    -------------------- ---- --------- ---- ------------------------------- 
    .\testscope.p             0     Procedure        No  
    .\testscope.p             1     For              No  
         Buffers: sports2000.Customer 
         Frames:  Unnamed 
    .\testscope.p             7     For              No  
         Buffers: sports2000.Customer 
         Frames:  Unnamed 
    

This listing file tells you that line 1 of the procedure starts a FOR block and that this block does not start a transaction (again, more on that in Chapter 16 "Updating Your Database and Writing Triggers"). The next line tells you what you need to know about scoping. The line that reads Buffers: sports2000.Customer tells you that the Customer buffer is scoped to this FOR block and that it used an unnamed frame that is also scoped to that block. Next you see that another FOR block begins at line 7. The Customer buffer is also (independently) scoped to that block and it has its own unnamed frame.

You could construct similar examples using any combination of strong- and weak-scoped buffer references. For example, here’s a variation on the test procedure that uses a DO FOR block with a strong scope to the Customer buffer:

DO FOR Customer: 
     FIND FIRST Customer WHERE CreditLimit > 60000. 
     DISPLAY CustNum NAME CreditLimit. 
END. 
FOR EACH Customer WHERE state = "NH" BY CreditLimit DESCENDING: 
     DISPLAY CustNum NAME CreditLimit. 
END. 

This procedure scopes the Customer buffer to each block in turn, just as the first example does.

Record Buffer Rule 2: You cannot nest two weak-scoped references to the same buffer.

For example, here’s a procedure that violates this rule:

DEFINE VARIABLE dLimit AS DECIMAL    NO-UNDO. 
FOR EACH Customer WHERE state = "NH" BY CreditLimit DESCENDING: 
     DISPLAY CustNum NAME CreditLimit. 
     dLimit = Customer.CreditLimit. 
     FOR EACH Customer WHERE CreditLimit > dLimit: 
          DISPLAY CustNum NAME CreditLimit. 
     END. 
END. 

If you try to run this procedure, you get the error shown in Figure 7–3 that tells you that your buffer references are invalid.

Figure 7–3: Invalid buffer references error message

When you think about it, this is perfectly sensible and necessary. Picture this situation:

Progress is using the Customer buffer for the current Customer record in the outer FOR EACH block. The first time through the block, it contains the New Hampshire Customer with the highest CreditLimit. Now suddenly Progress gets a request to use that same buffer to start another FOR EACH block, while it’s still in the middle of processing the outer one. This could not possibly work. If Progress replaced the New Hampshire Customer with whatever Customer was the first one to satisfy the selection of Customers with higher CreditLimits and then you had another reference to the first Customer later on in the outer block (which would be perfectly valid), that Customer record would no longer be available because Progress would have used the same buffer for the inner FOR EACH block. Because this can’t be made to work with both blocks sharing the same buffer at the same time, this construct is invalid.

Record Buffer Rule 3: A weak-scope block cannot contain any free references to the same buffer.

This rule makes sense for the same reasons as the second rule. Consider this example:

DEFINE VARIABLE dLimit AS DECIMAL     NO-UNDO. 
FOR EACH Customer WHERE state = "NH" BY CreditLimit DESCENDING: 
     DISPLAY CustNum NAME CreditLimit. 
     dLimit = Customer.CreditLimit. 
     FIND FIRST Customer WHERE CreditLimit > dLimit. 
     DISPLAY CustNum NAME CreditLimit. 
END. 

While Progress is processing the FOR EACH block, it gets a request to use the same buffer to find a completely unrelated record. This fails with a similar error, as shown in Figure 7–4.

Figure 7–4: FOR EACH processing error message

Record Buffer Rule 4: If you have a free reference to a buffer, Progress tries to scope that buffer to the nearest enclosing block with record scoping properties (that is, a FOR EACH block, a DO FOR block, or a REPEAT block). If no block within the procedure has record scoping properties, then Progress scopes the record to the entire procedure.

This rule also makes good sense when you think about it. The FIND statements are called free references because they don’t define a scope for the buffer, they just reference it. Therefore, Progress has to identify some scope for the record beyond the FIND statement. When a block has record scoping properties, it is a block Progress might try to scope a record to, when the record is referenced inside the block.

Here’s another variation on the testscope.p procedure that demonstrates this rule:

DEFINE VARIABLE dLimit AS DECIMAL     NO-UNDO INIT 0. 
FOR EACH Customer WHERE State = "NH" BY CreditLimit DESCENDING: 
     IF dLimit = 0 THEN 
        dLimit = Customer.CreditLimit. 
     DISPLAY CustNum NAME CreditLimit.    
END. 
FIND FIRST Customer WHERE CreditLimit > dLimit. 
DISPLAY CustNum NAME CreditLimit. 

This procedure is perfectly valid. The first time through the FOR EACH loop, the procedure saves off the CreditLimit for use later in the procedure. Because the dLimit variable is initialized to zero, checking for dLimit = 0 tells you whether it’s already been set. When you run it, you see all the New Hampshire Customer records followed by the first Customer with a CreditLimit higher than the highest value for New Hampshire Customers. Because there’s no conflict with two blocks trying to use the same buffer at the same time, it compiles and runs successfully.

But the rule that Progress raises the scope in this situation is a critically important one. In complex procedures, the combination of buffer references you use might force Progress to scope a record buffer higher in the procedure than you expect. Though this normally does not have a visible effect when you’re just reading records, when you get to the discussion of transactions this rule becomes much more important. If you generate another listing file for this procedure, you see the effect of the FIND statement:

{} Line Blk 
-- ---- --- 
     1    DEFINE VARIABLE dLimit AS DECIMAL     NO-UNDO INIT 0. 
     2      
     3    1   FOR EACH Customer WHERE State = "NH" BY CreditLimit DESCENDING: 
     4    1        IF dLimit = 0 THEN 
     5    1          dLimit = Customer.CreditLimit. 
     6    1        DISPLAY CustNum NAME CreditLimit.    
     7       END. 
     8      
     9    FIND FIRST Customer WHERE CreditLimit > dLimit. 
    10    DISPLAY CustNum NAME CreditLimit. 
    11      
     File Name          Line     Blk. Type     Tran          Blk. Label  
-------------------- ---- --------- ---- -------------------------------- 
.\testscope.p           0        Procedure     No                                     
     Buffers: sports2000.Customer 
     Frames:     Unnamed 
.\testscope.p           3        For          No 
     Frames:     Unnamed 

This tells you that the Customer buffer is scoped at line 0, that is, to the procedure itself. There’s no reference to the Customer buffer in the information for the FOR block at line 3 because Progress has already scoped the buffer higher than that block.

Next is the rule concerning combining FIND statements with strong-scoped, rather than weak-scoped references.

Record Buffer Rule 5: If you have a strong-scoped reference to a buffer, you cannot have a free reference that raises the scope to any containing block.

This rule also makes perfect sense. The whole point of using a strong-scoping form, such as a DO FOR block, is to force the buffer scope to that block and nowhere else. If Progress encounters some other statement (such as a FIND statement) outside the strong-scoped block that forces it to try to scope the buffer higher than the strong scope, it cannot do this because this violates the strong-scoped reference. Here’s an example:

DEFINE VARIABLE dLimit AS DECIMAL NO-UNDO. 
  
     DO FOR Customer: 
          FIND FIRST customer WHERE state = "MA". 
          DISPLAY CustNum NAME CreditLimit. 
          dLimit = Customer.CreditLimit. 
     END. 
     FIND FIRST Customer WHERE Customer.CreditLimit > dLimit. 
     DISPLAY CustNum NAME CreditLimit. 

If you try to run this procedure you get the error shown in Figure 7–5.

Figure 7–5: Conflicting table reference error message

Remember this distinction between Rule 1 and Rule 5. Rule 1 says that strong- and weak-scoped references in separate blocks are self-contained, so it is legal to have multiple blocks in a procedure that scope the same buffer to the block. Rule 5 tells you that it is not legal to have some other reference to the buffer that would force the scope to be higher than any of the strong-scoped references to it.

Here are a few more small examples that illustrate how these rules interact:

DEFINE VARIABLE iNum AS INTEGER     NO-UNDO INIT 0. 
DO FOR Customer: 
     FOR EACH Customer WHERE CreditLimit > 80000  
       BY CreditLimit DESCENDING: 
          DISPLAY CustNum NAME CreditLimit. 
          IF iNum = 0 THEN iNum = Customer.CustNum. 
     END. 
     FIND Customer WHERE CustNum = iNum. 
     DISPLAY NAME FORMAT "x(18)"  
          City FORMAT "x(12)"  
          State FORMAT "x(12)" 
          Country FORMAT "x(12)". 
END. 

This procedure displays all the Customers with CreditLimits over 80000, saving off the Customer number of the highest one. Figure 7–6 shows the first result.

Figure 7–6: Customers with CreditLimits over 80000 – first result

It then finds that Customer again with the highest CreditLimit and redisplays it with some more fields, as shown in Figure 7–7.

Figure 7–7: Customers with CreditLimits over 80000 – next result

This example illustrates that it is valid to have a weak-scoped block enclosed in a strong-scoped block. Progress raises the scope of the Customer buffer to the outer DO FOR block. This allows you to reference the buffer elsewhere in the DO FOR block, such as the FIND statement. The FIND statement raises the scope of the buffer to the DO FOR block, the nearest containing block with block-scoping properties.

Here’s another example that illustrates raising buffer scope:

REPEAT: 
     FIND NEXT Customer USE-INDEX NAME. 
     IF NAME < "D" THEN NEXT. 
     ELSE LEAVE. 
END. 
DISPLAY CustNum NAME. 

As it processes the procedure, Progress encounters the FIND statement and tentatively scopes the Customer buffer to the REPEAT block. The REPEAT block by itself does not force a buffer scope without a FOR phrase attached to it but it does have the record-scoping property, so it is the nearest containing block for the FIND statement. This block cycles through Customers in Name order and leaves the block when it gets to the first one starting with D. But after that block ends, Progress finds a free reference to the Customer buffer in the DISPLAY statement. This forces Progress to raise the scope of the buffer outside the REPEAT block. Since there is no available enclosing block to scope the buffer to, Progress scopes it to the procedure. Thus, the Customer buffer from the REPEAT block is available after that block ends to display fields from the record, as shown in Figure 7–8.

Figure 7–8: Raising buffer scope example result

This next procedure has two free references, each within its own REPEAT block:

REPEAT: 
     FIND NEXT Customer USE-INDEX NAME. 
     IF NAME BEGINS "D" THEN DO: 
          DISPLAY CustNum NAME WITH FRAME D. 
          LEAVE. 
     END. 
END. 
REPEAT: 
     FIND NEXT Customer USE-INDEX NAME. 
     IF NAME BEGINS "E" THEN DO: 
          DISPLAY CustNum NAME WITH FRAME E. 
          LEAVE. 
     END. 
END. 

As before, Progress initially scopes the buffer to the first REPEAT block. But on encountering another FIND statement within another REPEAT block, Progress must raise the scope to the entire procedure. The first block cycles through Customers until it finds and displays the first one whose name begins with D, and then leaves the block. Because the buffer is scoped to the entire procedure, the FIND statement inside the second REPEAT block starts up where the first one ended, and continues reading Customers until it gets to the first one beginning with E. Figure 7–9 shows the result.

Figure 7–9: Raising buffer scope example 2 result

This is a very important aspect of buffer scoping. Not only are both blocks using the same buffer, they are also using the same index cursor on that buffer. This is different from the earlier examples where multiple strong- or weak-scoped blocks scope the buffer independently. In these cases, each block uses a separate index cursor, so a second DO FOR or FOR EACH starts fresh back at the beginning of the record set. The difference is that the FIND statements inside these REPEAT blocks are free references, so they force Progress to go up to an enclosing block that encompasses all the free references.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095